package weeny.pre.face.treetable;

import static weeny.pre.face.ColorTextField.COLOR_FLD_PATTERN;
import static weeny.pre.io.parser.EnumParser.SHAPE_PARSER;

import java.awt.Component;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.event.TreeModelListener;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;

import prefuse.Visualization;
import prefuse.data.Table;
import prefuse.data.expression.Predicate;
import prefuse.visual.VisualItem;
import weeny.pre.face.ColorTextField;
import weeny.pre.face.EnumComboBox;

/**
 * 
 * @author weeny
 */
public abstract class AbstractPreModel implements TreeTableModel, TreeCellRenderer{
	protected Object root;
	public Object getRoot() {
		return root;
	}
	public boolean isLeaf(Object node) {
		return getChildCount(node) == 0; 
	}
	public void valueForPathChanged(TreePath path, Object newValue) {
		
	}
	public void addTreeModelListener(TreeModelListener l) {
		//throw new UnsupportedOperationException();
	}
	public void removeTreeModelListener(TreeModelListener l) {
		//throw new UnsupportedOperationException();
	}
	public Class getColumnClass(int column) { return Object.class; }
	public int getIndexOfChild(Object parent, Object child) {
		for (int i = 0; i < getChildCount(parent); i++) {
			if (getChild(parent, i).equals(child)) { 
				return i; 
			}
		}
		return -1; 
	}
	
	/**
	 * Please override this function if you want to access visualization from
	 * this class.
	 */
	public Visualization getVisualization(){
		return null;
	}
	
	
	private TableCellEditor      colorEditor = new ColorTextField();
	private TableCellRenderer  colorRenderer = new ColorTextField();
	private TableCellEditor      shapeEditor = new EnumComboBox(SHAPE_PARSER);
	private TableCellEditor     stringEditor = null;
	private TableCellRenderer stringRenderer = null;
	private TableCellEditor       boolEditor = null;
	private TableCellRenderer   boolRenderer = null;{
		JTable table   = new JTable();
		boolRenderer   = table.getDefaultRenderer(Boolean.class);
		boolEditor     = table.getDefaultEditor(Boolean.class);
		stringEditor   = table.getDefaultEditor(Object.class);
		stringRenderer = table.getDefaultRenderer(Object.class);
	}
	
	/**
	 * called by {@link PreJTreeTable} to decide the renderer for each cell,
	 * please override {@link #selectRenderer(Class, String)} if you want to
	 * define your own rule.
	 * @see #selectRenderer(Class, String)
	 */
	
	TableCellRenderer selectRenderer(AbstractTreeNode node, int column){
		Class type = node.getType(getColumnName(column));
		return selectRenderer(type, getColumnName(column));
	}
	
	/**
	 * called by {@link PreJTreeTable} to decide the editor for each cell,
	 * please override {@link #selectEditor(Class, String)} if you want to
	 * define your own rule.
	 * @see #selectEditor(Class, String)
	 */
	TableCellEditor selectEditor(AbstractTreeNode node, int column){
		Class type = node.getType(getColumnName(column));
		return selectEditor(type, getColumnName(column));
    }
	
	/**
	 * Please override this function if you want to define your own renderer for
	 * a specific cell type (you can only decide based on the cell type and the
	 * column name)
	 */
	protected TableCellRenderer selectRenderer(Class type, String field){
		if (type == Boolean.class || type == boolean.class) {
			return boolRenderer;
		}
		if (type == Integer.class || type == int.class) {
			if (COLOR_FLD_PATTERN.matcher(field).matches()){
				return colorRenderer;
			}
		}
		return stringRenderer;
	}
	
	/**
	 * Please override this function if you want to define your own editor for
	 * a specific cell type (you can only decide based on the cell type and the
	 * column name)
	 */
	protected TableCellEditor selectEditor(Class type, String field){
		if (type == Boolean.class || type == boolean.class) {
			return boolEditor;
		}
		else if (type == Integer.class || type == int.class) {
			if (COLOR_FLD_PATTERN.matcher(field).matches()) {
				return colorEditor;
			}
			if (field.equals(VisualItem.SHAPE)){
				return shapeEditor;
			}
		}
		return stringEditor;
	}
	
	public void setValueAt(Object aValue, Object node, int column){
		if (((AbstractTreeNode)node).toData(aValue, getColumnName(column))) {
			//do we really need to call this function?
//			updateTableUI();
		}
	}
	
	public boolean isCellEditable(Object node, int column){
		if (column == 0) {
			return true;
		}
		return ((AbstractTreeNode)node).isCelleEditable(getColumnName(column));
	}
	
	public Object getValueAt(Object node, int column){
		return ((AbstractTreeNode)node).toView(getColumnName(column));
	}
	
	public Object getChild(Object node, int index){
		return ((AbstractTreeNode)node).getChild(index);
	}
	
	public int getChildCount(Object node){
		return ((AbstractTreeNode)node).getChildCount();
	}
	
	/**
	 * For column selection purpose, please provide all possible names. 
	 */
	abstract protected List<String> getColumnNameCandidates();
	/**
	 * Please provide all possible table that are involved in this model
	 */
	abstract protected List<Table> getTableList();
	/**
	 * Please define the action when we set a filter to this tree, which should
	 * update the model. See {@link PreGroupModel#setFilter(Predicate)} for an
	 * example.
	 */
	abstract protected void setFilter(Predicate filter);
	
	/**
	 * To construct a model, a root is required by {@link JTree}, and a String
	 * array is required by {@link JTable}. Please notice that the first column
	 * is important, because it will act like a jtree, and the content in that
	 * column is used as tree label.
	 */
	public AbstractPreModel(AbstractTreeNode root, String...columns) {
		this.root = root;
		if (columns == null || columns.length == 0) {
			throw new IllegalArgumentException("The first column is tree, " +
					"which is cannot be obmitted!");
		}
		for (String string : columns) {
			columnNames.add(string);
		}
	}
	
	protected void updateTableUI(){
		if (jtable != null) {
//			treeRenderer.repaint();
			jtree.updateUI();
			jtable.invalidate();
			jtable.repaint();
		}
	}
	protected JTable jtable = null;
	protected JTree jtree = null;
	public void setTable(JTable table, JTree tree){
		this.jtable = table;
		this.jtree = tree;
	}
	
	private List<String> columnNames = new ArrayList<String>();
	public int getColumnCount() {
		return columnNames.size();
	}
	public String getColumnName(int column) {
		return columnNames.get(column);
	}
	public void updateColumn(String column, boolean include) {
		if (include) {
			columnNames.add(column);
		}
		else{
			columnNames.remove(columnNames.lastIndexOf(column));
		}
	}
	public int columnIndex(String name){
		return columnNames.lastIndexOf(name);
	}
	
	//------------------------------------------------------------------------//
	//                            For TreeCellRenderer                        //
	//------------------------------------------------------------------------//
	private DefaultTreeCellRenderer dftRenderer = new DefaultTreeCellRenderer();
	//we forward all the rendered component to the tree nodes and see if they
	//want to modify it
	public Component getTreeCellRendererComponent(JTree tree, Object value,
			boolean selected, boolean expanded, boolean leaf, int row,
			boolean hasFocus) {
		Component comp = dftRenderer.getTreeCellRendererComponent(tree, value, 
				selected, expanded, leaf, row, hasFocus);
		((AbstractTreeNode) value).setTreeIcon((JLabel) comp, expanded);
		return comp;
	}
}